#include "ns3/log.h"
#include "ns3/simulator.h"
#include <iostream>
#include "wfq-in-netdevice.h"

namespace ns3 {

  NS_LOG_COMPONENT_DEFINE("H_WFQQueue");

  NS_OBJECT_ENSURE_REGISTERED(H_WFQQueue);

  TypeId H_WFQQueue::GetTypeId(void) {
    static TypeId tid = TypeId("ns3::H_WFQQueue")
      .SetParent<Queue>()
      .SetGroupName("Network")
      .AddConstructor<H_WFQQueue>()
      ;
    return tid;
  }

  H_WFQQueue::H_WFQQueue() :
    Queue(),
    m_packets()
  {
    NS_LOG_FUNCTION(this);
    m_packets.resize(3);
    m_weight.push_back(3);
    m_weight.push_back(2);
    m_weight.push_back(1);
    std::vector<double> temp0(1, 0.0);
		std::vector<double> temp1(1, 0.0);
		std::vector<double> temp2(1, 0.0);
		m_finish.push_back(temp0);
    m_finish.push_back(temp1);
    m_finish.push_back(temp2);
    m_limit = 100;
    //initial the queue num of wfq
  }

  H_WFQQueue::~H_WFQQueue() {
    NS_LOG_FUNCTION(this);
  }

  //new dodequeue
  bool H_WFQQueue::DoEnqueue(Ptr<QueueItem> item) {
    NS_LOG_FUNCTION(this);
    Ptr<Packet> p = item->GetPacket();
    Ptr<Packet> pkt = item->GetPacket()->Copy();
    Ipv4Header ipHdr;
    UdpHeader udpHdr;
    pkt->RemoveHeader(ipHdr);
    pkt->RemoveHeader(udpHdr);
    uint8_t buffer[20];
    pkt->CopyData(buffer, pkt->GetSize());

    std::cout << "the receive buffer is: " << buffer << std::endl;
    //compare the content of buffer and defined flag
   /* if (strncmp((char*)buffer, "SetQueueNum", 11) == 0)
    {
      MTag queueNum;
      WeightTag WTag;
      pkt->PeekPacketTag(queueNum);
      pkt->PeekPacketTag(WTag);
      NS_LOG_INFO("WFQ.queueNum = " << (uint32_t)queueNum.GetSimpleValue());
      AddQueue(queueNum.GetSimpleValue());
      UpdateQueueWeight(WTag);
      std::cout << "WFQ.Content of Packet = " << std::endl;
      pkt->CopyData(&std::cout, pkt->GetSize());
      std::cout << std::endl;
      Drop(p);
    }*/
  if(1) // else
    {
      uint32_t tosValue = GetTosValue(item);
      uint32_t enqID = GetEnqID(tosValue);
      NS_LOG_INFO("The value of TOS is " << tosValue << ", so the packet(ID = " << pkt->GetUid() << ") will enter the queue " << enqID);

      if (m_packets[enqID].size() < m_limit)
        ComputeFinishTime(p, enqID);
      else
      {
        NS_LOG_LOGIC("The queue if full -- dropping pkt");
        Drop(item->GetPacket());
        return false;
      }

     /* if (!m_packets[enqID].push(item))
      {
        NS_LOG_LOGIC("Enqueue failed -- dropping pkt");
        Drop(p);
        return false;
      }*/

      NS_LOG_INFO(">> >> >> The packet(ID = " << p->GetUid() << ") enter the queue " << enqID << ", and the current number of packets in this queue is " << m_packets[enqID].size());

      return true;
    }
    //compare the content of buffer and defined flag 
  }

  Ptr<QueueItem> H_WFQQueue::DoDequeue(void) {
    NS_LOG_FUNCTION(this);

    uint32_t deqID = GetDeqID();

    Ptr<QueueItem> item;

    if (!m_packets[deqID].empty()) {
      item = m_packets[deqID].front();
      m_packets[deqID].pop();
      NS_LOG_INFO("<< << <<  The packet(ID = " << item->GetPacket()->GetUid() << ") departs from the queue " << deqID << " , and the current number of packets in this queue is " << m_packets[deqID].size());
    }
    else
      NS_LOG_INFO("Failed! All of the queues have been empty");
    return item;
  }

  /*Ptr<const QueueItem> H_WFQQueue::DboPeek(void) const {
  NS_LOG_FUNCTION (this);

  Ptr<const QueueItem> item;

  item = StaticCast<QueueDiscItem> (GetInternalQueue(0)->Dequeue ());
  if (item != 0) {
  std::cout << "+++++++++++Poped from band 0" << std::endl;
  std::cout << "+++++++++++Number packets band " << GetInternalQueue(0)->GetNPackets() << std::endl;
  }
  else{
  std::cout << "+++++++++++band 0 is empty" << std::endl;
  }

  return item;

  } */

/*  bool H_WFQQueue::AddQueue(uint32_t queueNum)
  {
    for (uint8_t i = 0; i < queueNum - m_packets.size(); i++)
    {
      std::queue<Ptr<QueueItem> emptyQueue;
      m_packets.push_back(emptyQueue);
      std::vector<double> new_finish(1, 0.0);
      m_finish.push_back(new_finish);
    }
    return true;
  }

  bool H_WFQQueue::UpdateQueueWeight(WeightTag WTag)
  {
    m_weight.erase(m_weight.begin(), m_weight.end());
    m_weight = WTag.GetWeightValue();

    // still can't update m_finish time.
  }
*/

  uint32_t H_WFQQueue::GetTosValue(Ptr<QueueItem> item) {
    NS_LOG_FUNCTION(this);

     Ipv4Header ipHeader;
		 item->GetPacket()->PeekHeader(ipHeader);

    return (uint32_t)ipHeader.GetTos();
  }

  uint32_t H_WFQQueue::GetEnqID(uint32_t tosValue) {
    NS_LOG_FUNCTION(this);

    return tosValue;
  }

  void H_WFQQueue::ComputeFinishTime(Ptr<Packet> pkt, uint32_t enqID) {
    NS_LOG_FUNCTION(this);

    double start = Simulator::Now().GetSeconds();
    if (start < m_finish[enqID].back())
      start = m_finish[enqID].back();

    uint32_t totalWeight = 0;
    for (uint32_t i = 0; i < m_weight.size(); i++)
      totalWeight += m_weight[i];

    double rate = 5000000 * double(m_weight[enqID]) / double(totalWeight);
    m_finish[enqID].push_back(start + (pkt->GetSize() / rate));
  }

  uint32_t H_WFQQueue::GetDeqID(void) {
    NS_LOG_FUNCTION(this);

    uint32_t deqID = 0;
    double min = -1;

    for (uint32_t i = 0; i < m_packets.size(); i++) {
      if (m_finish[i].size() > 1) {
        if (min < 0 || min > m_finish[i][1])
          min = m_finish[i][1];
      }
    }

    if (min >= 0) {
      for (uint32_t i = 0; i < m_packets.size(); i++) {
        if (m_finish[i].size() > 1)
          if (min == m_finish[i][1]){
            deqID = i;
            m_finish[i].erase(m_finish[i].begin());
          }
      }
    }
    std::cout << "the min finish time is: " << min << ", and the deqId is " << deqID << std::endl;
    return deqID;
  }
	Ptr<const QueueItem> H_WFQQueue::DoPeek(void) const {
	  NS_LOG_FUNCTION(this);
		  //NS_ASSERT(m_packets.size() == GetNPackets());
			  return m_packets[0].front();
	}

} // namespace ns3

